package flinty.autocomplete.combobox;

import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.text.BadLocationException;

/**
 * <code>AsyncComboBoxModel</code> is a model that should be used when querying
 * a list of appropriate items is a long-running task When filtering is queried
 * a new Thread is created. At the same time a fake dialog window with 1 * 1
 * pixel size is created that blocks any user action but don't block utility
 * background processes such as a repainting. Bad news is that this approach
 * does not work in the case of applets: for now I cannot find a reason why.
 * There for the constructor of <code>AsyncComboBoxModel</code> takes as a
 * parameter a flag which defines whether search task is to be executed in
 * separate thread
 * 
 * * @param <T> is a class of items displayed in combobox
 */
public class AsyncComboBoxModel<T> extends BaseAutoCompleteComboBoxModel<T> {

	private static final long serialVersionUID = 610768006858958816L;

	private static Logger logger = AutoCompleteComboBoxDecorator
			.createLogger(AsyncComboBoxModel.class);

	private final DataProvider<T> provider;

	private final StringConverter<T> converter;

	private final List<ItemWrapper<T>> filtered = new LinkedList<ItemWrapper<T>>();

	private final List<ItemWrapper<T>> preprocessed = new LinkedList<ItemWrapper<T>>();

	private final String waitText;

	private final boolean async;

	/**
	 * @param prov
	 *            provider of data that is to be displayed in combobox
	 * @param rend
	 *            converter that represent item of class T as a String
	 * @param waitTxt
	 *            text that displayed during search procedure
	 * @param asyncSearch
	 *            flag that determines whether the search is accomplished at new
	 *            thread (recommended for standard Java program) or at the same
	 *            thread (required for Applets)
	 */
	public AsyncComboBoxModel(final DataProvider<T> prov,
			final StringConverter<T> conv, final String waitTxt,
			final boolean asyncSearch) {
		provider = prov;
		converter = conv;
		waitText = waitTxt;
		async = asyncSearch;
	}

	@Override
	protected List<ItemWrapper<T>> getFilteredData() {
		return filtered;
	}

	@Override
	protected void loadData() {
		logger.fine("loading data");
		preprocessFiltering(null);
		updateFilteredData();
	}

	@Override
	protected boolean preprocessFiltering(final String pattern) {
		setUpdatePopup(true);
		boolean popupWasVisible = false;
		if (getDocument().getComboBox().isPopupVisible()) {
			popupWasVisible = true;
			getDocument().getComboBox().setPopupVisible(false);
		}
		String oldContent = null;
		try {
			oldContent = getDocument().getText(0, getDocument().getLength());
		} catch (BadLocationException e1) {
			logger.log(Level.SEVERE, "error", e1);
		}
		getDocument().setText(waitText);
		preprocessed.clear();
		if (async) {
			// TODO fix the behavior for applets
			final JDialog dialog = new JDialog(
					JOptionPane.getFrameForComponent(getDocument()
							.getComboBox()));
			dialog.setFocusable(false);
			dialog.setModal(true);
			dialog.setResizable(false);
			dialog.setUndecorated(true);
			dialog.setSize(new Dimension(1, 1));
			dialog.setLocationRelativeTo(getDocument().getComboBox());
			Thread thread = new Thread("querying") {
				@Override
				public void run() {
					for (T item : provider.getData(pattern)) {
						preprocessed.add(new ItemWrapper<T>(item,
								item != null ? converter.render(item) : null));
					}
					try {
						EventQueue.invokeAndWait(new Runnable() {

							@Override
							public void run() {
								dialog.setVisible(false);
							}
						});
					} catch (Exception e) {
						dialog.setVisible(false);
					}
					logger.fine("dialog is invisible");
				};
			};
			thread.setDaemon(true);
			thread.start();
			dialog.setVisible(true);
		} else {
			getDocument().getComboBox().paintImmediately(0, 0,
					getDocument().getComboBox().getWidth(),
					getDocument().getComboBox().getHeight());
			for (T item : provider.getData(pattern)) {
				preprocessed.add(new ItemWrapper<T>(item,
						item != null ? converter.render(item) : null));
			}
		}
		getDocument().setText(oldContent);
		if (popupWasVisible) {
			getDocument().getComboBox().setPopupVisible(true);
		}
		setUpdatePopup(false);
		logger.fine("thread finished");
		return preprocessed.size() > 0;
	}

	@Override
	protected void updateFilteredData() {
		logger.fine("updating filetered data");
		filtered.clear();
		filtered.addAll(preprocessed);
	}

	@Override
	protected void resetFilteredData() {
		logger.fine("resetting filetered data");
		data = null;
		filtered.clear();
		filtered.addAll(preprocessed);
	}

	@Override
	protected boolean isValid(ItemWrapper<T> anObject) {
		return anObject == null || filtered.contains(anObject);
	}

}
